#!/usr/bin/env python

"""
- Remember to change the above line to fit your environment
- Python >= 2.5 is required
- Read the LICENSE file before redistributing/modifying this software
"""

"""
Constants and globals
"""
VERSION = '0.6.1-alpha'
AUTHORS = 'Federico Maggi <fmaggi@elet.polimi.it>'
(popts, pargs) = (None, None)

"""
Imports
"""
import os
import sys
import covering
import sigma
import fsm
import qelme

"""
Useful methods
"""
def parse_input():
    from optparse import OptionParser
    import time
    """
    Input parsing stuff
    """
    p = OptionParser(
	version = '\nQELME - Quick Earley-Like Membership Evaluator.\nVer. %s\n(c)2007-%d %s, BSD License\n'%(VERSION, int(time.strftime('%Y')), AUTHORS),
	usage = '%prog options\n'
	)
    p.add_option(
	'-a',
	'--automaton',
	help = 'The automaton.'
	)
    p.add_option(
	'-c',
	'--covering',
	help = 'The clique covering.'
	)
    p.add_option(
	'-l',
	'--random-string-length',
        type = 'int',
	help = 'The length of the random generated string.'
	)
    p.add_option(
	'-u',
	'--string',
	help = 'The string, comma separated!'
	)
    p.add_option(
	'-p',
	'--code-profiling',
        action = 'store_true',
	default = False,
	help = 'Turn on profiling. Off by default.'
	)
    p.add_option(
        '-e',
	'--evaluations',
        type = 'int',
        default = 1,
	help = 'Turn on performance evaluation (none or positive integer). Example (non-default): 10 (ten simulations)'
        ),
    p.add_option(
        '-f',
	'--min-sigma-size',
        type = 'int',
	help = 'Random alphabet minimum size (more or equal than %d).'
        )
    p.add_option(
        '-s',
	'--max-sigma-size',
        type = 'int',
	help = 'Random alphabet maximum size (more or equal than %d).'
        )
    p.add_option(
        '-b',
        '--back-size',
        type = 'int',
        help = 'The number of back edges in the generated local automaton: it does make sense in eval-mode only.'
        )
    p.add_option(
        '-g',
        '--save-automaton',
        default = False,
        action = 'store_true',
        help = 'Save the automaton in PNG. Off by default.'
        )
    p.add_option(
        '-m',
        '--string-randomization',
        default = 'accepted',
        help = 'Test the algorithm on mutated string: choices are \'random\', \'accepted\' (default), \'mutated\', \'trace\''
        )
    p.add_option(
        '-z',
        '--alphabet',
        help = 'Specify a fixed alphabet to be used in the evaluation.'
        )
    p.add_option(
        '-o',
        '--output-file',
        help = 'Output file (prefix) for saving symulation results. Only in evaluation mode. Example (non-default): "foo"'
        )
    p.add_option(
        '-d',
        '--covering-density',
        type = 'float',
        help = 'In the case of random generated covering, a "density" metric can be specified as the float |C|/|Alphabet|. Example (non-default): 0.6',
        default = 0.5
        )
    p.add_option(
        '-D',
        '--covering-exact-density',
        action = 'store_true',
        default = False,
        help = 'In the case of random generated covering, this turns off further randomization'
        )
    p.add_option(
        '-x',
        '--latex-report',
        action = 'store_true',
        default = False,
        help = '[NOT IMPLEMENTED YET] Generate a report in LaTeX'
        )
    p.add_option(
        '-v',
        '--verbose',
        action = 'store_true',
        default = False,
        help = 'Be verbose'
        )
    p.add_option(
        '-V',
        '--more-verbose',
        action = 'store_true',
        default = False,
        help = 'Be even more verbose'
        )

    (popts, pargs) = p.parse_args()

    if popts.alphabet:
        popts.alphabet = popts.alphabet.split(',')
    if popts.string:
        popts.string = popts.string.split(',')

    return (popts, pargs)

# Parse the input arguments/options
(popts, pargs) = parse_input()

"""
The main
"""
def main():
    # Turn on profiling
    if popts.code_profiling:
        if popts.verbose:
            print 'Code profiling is ON'
        import cProfile, pstats
        times, coversize, strlen, backsize, sigmasize, strings, alpha = {True: [], False: []}, {True: [], False: []}, {True: [], False: []}, {True: [], False: []}, {True: [], False: []}, {True: [], False: []}, {True: [], False: []}

    # Run each simulation
    for k in range(0, popts.evaluations):
        if popts.verbose:
            print 'Simulation %d of %d'%(k+1, popts.evaluations)
        """
        Random or fixed alphabet?
        """
        if popts.alphabet is None:
            #random, but with a limited number of symbols
            if popts.min_sigma_size and popts.max_sigma_size:
                S = sigma.Sigma(min = popts.min_sigma_size, max = popts.max_sigma_size)
            elif popts.max_sigma_size:
                S = sigma.Sigma(max = popts.max_sigma_size)
            else:
                #random with max. 1000
                S = sigma.Sigma()
            if popts.verbose:
                print 'Using a random alphabet (could change):',
        else:
            S = sigma.Sigma(symbols = popts.alphabet)
            if popts.verbose:
                print 'Using the given alphabet (could change):',
        if popts.verbose:
            print S, 'of size', len(S)

        """
        Random or fixed covering?
        """
        if popts.covering is None:
            if popts.verbose:
                print 'Using a random covering',
            if popts.covering_density:
                C = S.rcov(density = popts.covering_density, exact = popts.covering_exact_density)
                if popts.verbose:
                    print '(chosen density: %f, actual density: %f):'%(popts.covering_density, C.density),
            else:
                C = S.rcov()
                if popts.verbose:
                    print ':',
        else:
            # Fix the alphabet according to the provided covering
            S = sigma.Sigma(symbols = [x for x in set(popts.covering.replace(';', ',').split(','))])
            # Create the covering object
            C = covering.Covering(spec = popts.covering, sigma = S)

        if popts.verbose:
            print C
            print 'Alphabet (maybe changed):', S

        """
        Random or fixed automaton?
        """
        if popts.automaton is None:
            if popts.verbose:
                print 'Using a random automaton:',
            if popts.back_size is None:
                import random
                b = random.randint(0, int(len(S)/2))
            else:
                b = popts.back_size
            A = fsm.FSM(spec = None, sigma = S, back_size = b)
        else:
            A = fsm.FSM(spec = popts.automaton)
            b = -1 #Meaning that it is a non-local automaton
        if popts.verbose:
            print A

        """
        Random or fixed string?
        """
        if popts.string is None:
            if popts.random_string_length is None:
                import random
                l = random.randint(10, 1000)
                if popts.verbose:
                    print 'Trying with a random string length of:',
            else:
                l = popts.random_string_length

            if popts.verbose:
                print l

            if popts.string_randomization is not None:
                if popts.string_randomization == 'accepted':
                    U = A.accepted_string(length = l)
                    if popts.verbose:
                        print 'Using an accepted string:',
                elif popts.string_randomization == 'mutated':
                    U = A.mutated_accepted_string(length = l)
                    if popts.verbose:
                        print 'Using an accepted string, randomly mutated:',
                elif popts.string_randomization == 'trace':
                    UT = A.random_trace(C, length = l)
                    if popts.verbose:
                        print 'Original string:', UT[0],
                        if str(UT[0]) != str(UT[1]):
                            print '(changed)'
                        else:
                            print
                    U = UT[1]
                    if popts.verbose:
                        print 'Using an accepted trace:',
                else:
                    U = A.random_string(length = l)
                    if popts.verbose:
                        print 'Using a completely random string:',
            else:
                U = A.random_string(length = l)
                if popts.verbose:
                    print 'Using a completely random string:',
        else:
            U = popts.string
            if popts.verbose:
                print 'Using a chosen, fixed string:',

        if popts.verbose:
            print ','.join(U)
            print 'The actual string length, limited by the automaton size, is: %d'%len(U)
            print 'Run string: %s -v -e 1 -a \'%s\' -c \'%s\' -u \'%s\' -z \'%s\''%(os.path.basename(sys.argv[0]), str(A), str(C), ','.join(U), str(S))

        """
        Dump the automaton in PNG?
        """
        if popts.save_automaton:
            if popts.verbose:
                print 'Printing the automaton to \'automaton_%d.png\''%(k+1)
            A.dotit(file = 'automaton_%d.png'%(k+1))

        if popts.code_profiling:
            """
            Execution and profiling
            """
            if popts.verbose:
                print 'Code profiling is: ON'
            pr = cProfile.Profile()
            if popts.verbose:
                print 'Running the decisor:'
            decisor = qelme.Decisor(code_profiling = True, string = U, automaton = A, covering = C, backsize = b, verbose = popts.more_verbose)

            """
            Storing performance metrics
            """
            times[decisor.decision].append(decisor.statistics['time'])
            coversize[decisor.decision].append(decisor.statistics['coversize'])
            strlen[decisor.decision].append(decisor.statistics['strlen'])
            backsize[decisor.decision].append(decisor.statistics['backsize'])
            sigmasize[decisor.decision].append(decisor.statistics['sigmasize'])
            alpha[decisor.decision].append(decisor.statistics['alpha'])
        else:
            if popts.verbose:
                print 'Code profiling is: OFF'
                print 'Running the decisor:'
            decisor = qelme.Decisor(string = U, automaton = A, covering = C, backsize = b, verbose = popts.more_verbose)

        if popts.verbose:
            print 'Decision: ' + str(decisor.decision)

    if popts.code_profiling:
        if popts.output_file:
            outfile = open('%s.csv'%popts.output_file, 'a')
            outfile.write('result, time, coversize, sigmasize, strlen, backsize, density, alpha\n')
        print 'result, time, coversize, sigmasize, strlen, backsize, density, alpha'
        for d in [True, False]:
            for t,c,s,u,b,a in zip(times[d], coversize[d], sigmasize[d], strlen[d], backsize[d], alpha[d]):
                print '%s, %f, %d, %d, %d, %d, %f, %d'%(d,t,c,s,u,b,float(c)/float(s),a)
                if popts.output_file:
                    outfile.write('%s, %f, %d, %d, %d, %d, %f, %a\n'%(d,t,c,s,u,b,float(c)/float(s)),a)
        if popts.output_file:
            if popts.verbose:
                print 'Writing out to file: %s.csv'%popts.output_file
            outfile.close()
    return 1


"""
Invocation
"""
if __name__ == '__main__':
    sys.exit(main())
